package andev.lopengles.Renderer;

import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * @author Angus
 *         the GL10 instance passed in is referred to as glUnused.
 *         We don't use this when drawing using OpenGL ES 2; instead,
 *         we use the static methods of the class GLES20.
 *         The GL10 parameter is only there because the same interface is used for OpenGL ES 1.x.
 */
public class Renderer1 implements Renderer {
    // How many bytes per float.
    private final int mBytesPerFloat = 4;
    /**
     * How many elements per vertex.
     */
    private final int mStrideBytes = 7 * mBytesPerFloat;
    /**
     * Offset of the position data.
     */
    private final int mPositionOffset = 0;
    /**
     * Size of the position data in elements.
     */
    private final int mPositionDataSize = 3;
    /**
     * Offset of the color data.
     */
    private final int mColorOffset = 3;
    /**
     * Size of the color data in elements.
     */
    private final int mColorDataSize = 4;
	/* 
	 * This method is called when the surface is first created. It will also be called if we lose our surface context and it is later recreated by the system.
	 */
    // Store our model data in a float buffer.
    private FloatBuffer mTriangle1Vertices;
    private FloatBuffer mTriangle2Vertices;
    private FloatBuffer mTriangle3Vertices;
    //View matrix. This can be thought of as our camera. This matrix transforms world space to eye space;it positions things relative to our eye.
    private float[] mViewMatrix = new float[16];
    /**
     * This will be used to pass in the transformation matrix.
     */
    private int mMVPMatrixHandle;
    /**
     * This will be used to pass in model position information.
     */
    private int mPositionHandle;
    /**
     * This will be used to pass in model color information.
     */
    private int mColorHandle;
    /**
     * Store the projection matrix. This is used to project the scene onto a 2D viewport.
     */
    private float[] mProjectionMatrix = new float[16];

    // New class members
    /**
     * Store the model matrix. This matrix is used to move models from object space (where each model can be thought
     * of being located at the center of the universe) to world space.
     */
    private float[] mModelMatrix = new float[16];
    /**
     * Allocate storage for the final combined matrix. This will be passed into the shader program.
     */
    private float[] mMVPMatrix = new float[16];

    public Renderer1() {
        final float[] triangle1VerticesData =
                {
                        //X,Y,Z
                        //R,G,B,A
                        -0.5f, -0.25f, 0.0f,
                        1.0f, 0.0f, 0.0f, 1.0f,

                        0.5f, -0.25f, 0.0f,
                        0.0f, 0.0f, 1.0f, 1.0f,

                        0.0f, 0.559016994f, 0.0f,
                        0.0f, 1.0f, 0.0f, 1.0f
                };

		/* Initialize the buffers for passing data as buffer into OpenGL
		 * Allocate byte buffer and order buffer use native order(Java and the native system might not store their bytes in the same order,OpenGL ES 2 is actually written in C)
		 * then convert byte buffer to float buffer
		 * finally set the data to the float buffer
		 */
        mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTriangle1Vertices.put(triangle1VerticesData).position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // Set the background clear color to gray.
        GLES20.glClearColor(1.0f, 0.5f, 0.5f, 0.5f);

        //set up the view matrix
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 2, 0, 0, -2, 0, 1, 0);

        //vertex shader
        int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        if (vertexShaderHandle == 0)
            Log.v("", "Create Shader Failed" + "\n:" + GLES20.glGetError());
        if (vertexShaderHandle != 0) {
            final String vertexShader =
                    "uniform mat4 u_MVPMatrix;        \n"     // A constant representing the combined model/view/projection matrix.

                            + "attribute vec4 a_Position;     \n"     // Per-vertex position information we will pass in.
                            + "attribute vec4 a_Color;        \n"     // Per-vertex color information we will pass in.

                            + "varying vec4 v_Color;          \n"     // This will be passed into the fragment shader.

                            + "void main()                    \n"     // The entry point for our vertex shader.
                            + "{                              \n"
                            + "   v_Color = a_Color;          \n"     // Pass the color through to the fragment shader.
                            // It will be interpolated across the triangle.
                            + "   gl_Position = u_MVPMatrix   \n"     // gl_Position is a special variable used to store the final position.
                            + "               * a_Position;   \n"     // Multiply the vertex by the matrix to get the final point in
                            + "}                              \n";    // normalized screen coordinates.

            // Pass in the shader source.
            GLES20.glShaderSource(vertexShaderHandle, vertexShader);
            // Compile the shader.
            GLES20.glCompileShader(vertexShaderHandle);
            // Get the compilation status.
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            if (compileStatus[0] == 0) {
                GLES20.glDeleteShader(vertexShaderHandle);
                vertexShaderHandle = 0;
            }
        } else {
            throw new RuntimeException("Error Create VertexShader");
        }

        //fragment shader
        int fragmentShaderHandle = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        if (fragmentShaderHandle != 0) {
            final String fragmentShader =
                    "precision mediump float;       \n"     // Set the default precision to medium. We don't need as high of a
                            // precision in the fragment shader.
                            + "varying vec4 v_Color;          \n"     // This is the color from the vertex shader interpolated across the
                            // triangle per fragment.
                            + "void main()                    \n"     // The entry point for our fragment shader.
                            + "{                              \n"
                            + "   gl_FragColor = v_Color;     \n"     // Pass the color directly through the pipeline.
                            + "}                              \n";
            // Pass in the shader source.
            GLES20.glShaderSource(fragmentShaderHandle, fragmentShader);
            // Compile the shader.
            GLES20.glCompileShader(fragmentShaderHandle);
            // Get the compilation status.
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(fragmentShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            if (compileStatus[0] == 0) {
                GLES20.glDeleteShader(fragmentShaderHandle);
                fragmentShaderHandle = 0;
            }
        } else {
            throw new RuntimeException("Error Create VertexShader");
        }

        //program object(A program object is an object to which shader objects can be attached)
        int programHandle = GLES20.glCreateProgram();
        if (programHandle != 0) {
            // Bind the vertex shader and fragment shader to the program.
            GLES20.glAttachShader(programHandle, vertexShaderHandle);
            GLES20.glAttachShader(programHandle, fragmentShaderHandle);

            //Bind attributes
            GLES20.glBindAttribLocation(programHandle, 0, "a_Position");
            GLES20.glBindAttribLocation(programHandle, 1, "a_Color");

            // Link the two shaders together into a program.
            GLES20.glLinkProgram(programHandle);

            //check the program link status
            final int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] == 0) {
                Log.v("", "Link Program Failed" + "\n:" + GLES20.glGetError());
                GLES20.glDeleteProgram(programHandle);
                programHandle = 0;
            }
            Log.v("", "Link Program Success" + "\n:" + GLES20.glGetError());
        } else {
            throw new RuntimeException("Error creating program.");
        }

        // Set program handles. These will later be used to pass in values to the program.
        mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
        mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
        mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");

        // Tell OpenGL to use this program when rendering.
        GLES20.glUseProgram(programHandle);
    }

    /*
     * This is called whenever the surface changes; for example, when switching from portrait to landscape. It is also called after the surface has been created.
     */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        // Create a new perspective projection matrix. The height will stay the same
        // while the width will vary as per aspect ratio.
        final float ratio = (float) width / height;
        final float left = -ratio;
        final float right = ratio;
        final float bottom = -1.0f;
        final float top = 1.0f;
        final float near = 1.0f;
        final float far = 10.0f;

        Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
    }

    /*
     * This is called whenever it��s time to draw a new frame.
     */
    @Override
    public void onDrawFrame(GL10 gl) {
        //fill the screen with the color previously defined by our call to glClearColor()
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // Do a complete rotation every 10 seconds.
        long time = SystemClock.uptimeMillis() % 10000L;
        float angleInDegrees = (360.0f / 10000.0f) * ((int) time);

        // Draw the triangle facing straight on.
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
        drawTriangle(mTriangle1Vertices);
    }

    /**
     * Draws a triangle from the given vertex data.
     *
     * @param aTriangleBuffer The buffer containing the vertex data.
     */
    private void drawTriangle(final FloatBuffer aTriangleBuffer) {
        // Pass in the position information
        aTriangleBuffer.position(mPositionOffset);
        GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, aTriangleBuffer);

        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Pass in the color information
        aTriangleBuffer.position(mColorOffset);
        GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, aTriangleBuffer);

        GLES20.glEnableVertexAttribArray(mColorHandle);

        // This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
        // (which currently contains model * view).
        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);

        // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
        // (which now contains model * view * projection).
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);

        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
    }
}
